/******************************************************************************

 @File         OGLES2Navigation3D.cpp

 @Title        Navigation

 @Version      

 @Copyright    Copyright (C)  Imagination Technologies Limited.

 @Platform     Independent

 @Description  Demonstrates a method of rendering a 3D navigation application
               using OpenGL ES 2.0 and various culling techniques

******************************************************************************/

#include "PVRShell.h"
#include "OGLES2Tools.h"

#include <stddef.h>

/******************************************************************************
 Defines
******************************************************************************/

#ifdef _DEBUG
#define ENABLE_CULLING_OPTIONS 
#define ENABLE_ADVANCED_OUTPUT
#endif

// Index the attributes that are bound to vertex shaders
#define VERTEX_ARRAY	 0
#define NORMAL_ARRAY	 1
#define TEXCOORD_ARRAY   2

/****************************************************************************
** Structures
****************************************************************************/

/*!***********************************************************************
 *	@Struct PVRTModelVertex
 ************************************************************************/
struct PVRTModelVertex
{
	PVRTVec3 position;
	PVRTVec3 normal;
	PVRTVec2 texcoord;
};

/*!***********************************************************************
 *	@Struct PVRTBoundingBox2D
 *	@Brief  Structure describing a 2D bounding box. Supports all kind of
 *          set operations and provides higher level functionality.
 ************************************************************************/
struct PVRTBoundingBox2D
{
	// Min and max coordinates
	PVRTVec2 minCoords;
	PVRTVec2 maxCoords;
};

/*!**************************************************************************
 *	@Struct ModelTileObjectSet
 ****************************************************************************/
struct ModelTileObjectSet
{
	PVRTBoundingBox2D boundingbox;
	size_t            numSubObjects;
	unsigned int     *pNodeIdx;
	unsigned int     *paNumIndices;
	unsigned int     *paIndexOffsets;
	GLuint           *pauiTextures;		
};

/*!**************************************************************************
 *	@Struct ModelTileLod
 *  @Brief  A model tile LOD stores the filename of the POD file and various
 *          attributes like the number of (parent) objects it contains.
 *          Furthermore it stores two OpenGL buffer object identifiers, one
 *          for the vertex and another one for the index data.
 *          The last members keep a list of visible nodes which are determined 
 *          in a seperate visibility update pass.
 ****************************************************************************/
struct ModelTileLod
{
	char               *pszFilename;
	size_t              numObjects;
	ModelTileObjectSet *paObjects;
	GLuint              vbos[2];
	unsigned int       *paVisibleNodes;
	unsigned int        numVisibleNodes;
	bool                bLoaded;
};

/*!**************************************************************************
 *	@Struct ModelTile
 *	@Brief  A model tile is a container for all models located within a certain
 *          region of the city defined by the bounding box. There can be several
 *          levels of detail for each model tile.
 ****************************************************************************/
struct ModelTile
{
	PVRTBoundingBox2D boundingbox;
	size_t            numLod;
	ModelTileLod     *paLod;
};

/*!**************************************************************************
 *	@Struct PositionOcclusionData
 *	@Brief  TODO
 ****************************************************************************/
struct PositionOcclusionData
{
	PVRTVec3 position;
	unsigned int numRefObjects;
	unsigned int *pRefTile;	
	unsigned int *pNumRefObject;	
	unsigned int **ppRefObjects;	
};

/*!**************************************************************************
 *	@Struct ShaderDescription
 *	@Brief  This structure describes a shader program and is just used to 
 *          conveniently store each available shader.
 ****************************************************************************/
struct ShaderDescription
{
	const char  *pszVertShaderSrcFile;
	const char  *pszVertShaderBinFile;
	const char  *pszFragShaderSrcFile;
	const char  *pszFragShaderBinFile;
	const unsigned int ui32NumAttributes;
	const char  **pszAttributes;	
};

/******************************************************************************
 Content file names
******************************************************************************/

const char *g_pszAttributes[] = { "inVertex", "inNormal", "inTexCoord" };

const ShaderDescription BuildingShaderDescription = { "BuildingVertShader.vsh", "BuildingVertShader.vsc", "BuildingFragShader.fsh", "BuildingFragShader.fsc",  3, (const char**)g_pszAttributes };
const ShaderDescription SkyboxShaderDescription   = { "SkyboxVertShader.vsh",   "SkyboxVertShader.vsc",   "SkyboxFragShader.fsh",   "SkyboxFragShader.fsc",    1, (const char**)g_pszAttributes };

// Textures
const char c_szTextureNameSkybox[] = "Skybox.pvr";

// Scene files
const char c_szTrackFile[] = "cameratrack.pod";
const char c_szModelIndexFilename[] = "modelindex.nav";
const char c_szOcclusionDataFilename[] = "occlusiondata.nav";

// Texture files required for the city model
const char *c_paszTextures[] = { "006_RUS.PNG", "007_RUG.PNG", "008_RUG.PNG", "009_RUG.PNG", "011_GIE.PNG", "012_RSR.PNG", "016_FOC.PNG", "016_RTR.PNG", "017_FOD.PNG", 
                                 "018_FOD.PNG", "019_FOC.PNG", "019_GOC.PNG", "019_RZG.PNG", "020_FOC.PNG", "021_FOC.PNG", "022_FOC.PNG", "022_RUG.PNG", 
                                 "023_FOB.PNG", "023_RUG.PNG", "024_FOB.PNG", "025_FOC.PNG", "025_RUW.PNG", "026_FOD.PNG", "026_RUW.PNG", "027_FOD.PNG", 
                                 "027_RUW.PNG", "028_GOF.PNG", "029_GCC.PNG", "030_GOC.PNG", "031_GOD.PNG", "032_FOC.PNG", "032_GOC.PNG", "033_FOA.PNG", 
                                 "033_GOA.PNG", "034_FOC.PNG", "034_GOC.PNG", "035_FOC.PNG", "035_GOC.PNG", "036_FOC.PNG", "036_GOC.PNG", "037_FOC.PNG", 
                                 "037_GOC.PNG", "041_FRB.PNG", "041_GRB.PNG", "044_GRC.PNG", "046_GRC.PNG", "055_GRC.PNG", "056_GRC.PNG", "060_FRC.PNG", "063_GRC.PNG", 
                                 "064_GRC.PNG", "066_FCB.PNG", "066_GCB.PNG", "067_FCC.PNG", "067_GCC.PNG", "068_GCD.PNG", "069_FCA.PNG", "069_GCA.PNG", 
                                 "070_GOD.PNG", "071_FRC.PNG", "072_FRC.PNG", "073_FRC.PNG", "074_FRC.PNG", "075_FRC.PNG", "076_FRC.PNG", "077_FRC.PNG", 
                                 "080_GCB.PNG", "083_FRC.PNG", "085_GRC.PNG", "086_FOF.PNG", "086_GOF.PNG", "087_FCA.PNG", "087_GCA.PNG", "087_GCC.PNG",                                  
								 "054_GRC.PNG", "059_FRC.PNG", "061_FRD.PNG", "082_FCD.PNG", "088_FRC.PNG", "089_FRC.PNG", "092_GCA.PNG", "094_FOD.PNG", "095_FOD.PNG",   
								 "US_IL_CHICAGO_MMART_L.PNG", "US_IL_13443_CHICAGO_35EAST_L.PNG", "US_IL_13444_CHICAGO_LEOBURNETT_L.PNG", 
                                 "US_IL_13447_CHICAGO_REIDMURDOCH_L.PNG", "US_IL_13448_CHICAGO_CARBIDE_L.PNG", "US_IL_13449_CHICAGO_CROWNFOUNTAIN_L.PNG", 
                                 "US_IL_13451_CHICAGO_CULTURAL_L.PNG", "US_IL_13453_CHICAGO_PRUDENTIAL_PART1_L.PNG", "US_IL_13454_CHICAGO_UNITED_L.PNG", 
								 "US_IL_13458_CHICAGO_SMURFIT_L.PNG", "US_IL_13459_CHICAGO_LASALLE_L.PNG", "US_IL_13461_CHICAGO_UNITRIN_L.PNG", "US_IL_13462_CHICAGO_WILLOUGHBY_L.PNG", 
								 "US_IL_13490_CHICAGO_PRUDENTIAL_PART2_L.PNG", "US_IL_CHICAGO_AONCENTER_L.PNG", "US_IL_CHICAGO_ARTINSTITUTE_L.PNG", "US_IL_CHICAGO_BOARDOFTHETRADE_L.PNG", 
								 "US_IL_CHICAGO_BOEINGBUILDING_L.PNG", "US_IL_CHICAGO_CHICAGOTHEATRE_L.PNG", "US_IL_CHICAGO_CITYHALL_L.PNG", "US_IL_CHICAGO_DALEY_L.PNG", 
								 "US_IL_CHICAGO_HILTON_L.PNG", "US_IL_CHICAGO_JAMESTHOMPSON_L.PNG", "US_IL_CHICAGO_LIBRARY_L.PNG", "US_IL_CHICAGO_MILLENIUMPARK1_L.PNG", 
								 "US_IL_CHICAGO_MILLENIUMPARK2_L.PNG", "US_IL_CHICAGO_OGILVIE_L.PNG", "US_IL_CHICAGO_SEARSTOWER_L.PNG", "US_L_CONCRETE-COLOUR.PNG", 
								 "US_L_CONCRETE-DETAIL.PNG", "US_L_PARK-COLOUR.PNG", "US_L_WATER-COLOUR.PNG", "US_R_CONCRETE.PNG", "US_R_STREET-DASHED.PNG", "US_R_STREET-INNER-SHOULDER.PNG", 
								 "US_R_STREET-LANE-FILLER.PNG", "US_R_STREET-SOLID.PNG", "US_R_STREET-UNMARKED.PNG", "US_R_WALKWAY-SOLID.PNG", "US_R_WALKWAY-UNMARKED.PNG", "US_T_RAILROAD.PNG",
                                 "US_R_HIGHWAY-SOLID.PNG", "US_IL_CHICAGO_UNIONSTATION_L.PNG", "US_IL_13460_CHICAGO_TRUMP_L.PNG", "US_IL_13456_CHICAGO_SEVENTEENTH_L.PNG" }; 


/*!****************************************************************************
 Class declarations
******************************************************************************/

/*!***********************************************************************
 *	@Class  OGLES2Navigation3D
 *	@Brief  Navigation demo main class.
 ************************************************************************/
class OGLES2Navigation3D : public PVRShell
{
	// Print3D class used to display text
	CPVRTPrint3D	   m_Print3D;	

	// Camera attributes	
	CPVRTModelPOD      m_CameraPod;
	unsigned int       m_ActiveCameraTrack;

	float              m_fFOV;
	float              m_fAspectRatio;
	float              m_fNearClipPlane;
	float              m_fFarClipPlane;
	PVRTVec3           m_vCameraFrom;
	PVRTVec3           m_vCameraTo;
	PVRTVec3           m_vCameraUp;	
		
	// Viewing matrices
	PVRTMat4           m_mViewMatrix;	
	PVRTMat4           m_mProjectionMatrix;
	PVRTMat4           m_mViewProjectionMatrix;		

	// 3D Models		
	unsigned int       m_uiNumModelTiles;
	ModelTile         *m_paModelTiles;
	unsigned int      *m_pauiVisibleTiles;
	unsigned int       m_uiNumVisibleTiles;

	float              m_afLodDistances[2];
	float              m_afSquaredLodDistances[2];

	// Occlusion data
	PositionOcclusionData *m_paPositionOcclusionData;
	unsigned int           m_uiNumOcclusionData;	

	// Skybox 
	GLuint             m_uiSkyboxVBO;
	GLuint             m_uiTextureIdSkybox;

	// Textures
	unsigned int       m_uiNumTextures;	
	GLuint            *m_pauiTextureIds;

	// Shader objects
	struct Shader
	{
		GLuint uiId;
		GLuint uiVertexShaderId;
		GLuint uiFragmentShaderId;
		GLuint uiModelViewProjMatrixLoc;
	}
	m_SkyboxShader;	

	struct ModelShader : Shader
	{				
		GLuint uiLightDirectionLoc;
	} 
	m_BuildingShader;	
	
	// Time variables
	float m_fCameraAnimation;
	float m_fDebugTimeMultiplier;

	// General options
	bool  m_bUseOcclusionData;
	bool  m_bViewFrustumCulling;
	bool  m_bPause;
	bool  m_bRotate;

public:

	virtual bool InitApplication();
	virtual bool InitView();
	virtual bool ReleaseView();
	virtual bool QuitApplication();
	virtual bool RenderScene();

	bool LoadTextures(CPVRTString* pErrorStr);
	bool LoadShader(CPVRTString* pErrorStr, const ShaderDescription &descr, Shader &shader);
	bool LoadShaders(CPVRTString* pErrorStr);
	bool ReleaseShader(Shader &shader);

	unsigned int UpdateTimer();
	void Update3dModelWorkingset();
	void HandleInput();

	void Render3dModels();
	void Render3dModelsOcclusion();
	void RenderSkyBox();

	void CreateModelVbo(const unsigned int tile, const unsigned int lod);
	void ReleaseModelVbo(const unsigned int tile, const unsigned int lod);
	
	void CalculateCameraMatrices();
	void GetCameraFrame(PVRTVec3 &from, PVRTVec3 &to, PVRTVec3 &up, float time);

	void ExtractViewFrustumPlanes(PVRTVec4 &left, PVRTVec4 &right, PVRTVec4 &front, PVRTVec4 &back) const;
	bool BoundingBoxIntersectsFrustum(const PVRTBoundingBox2D &bbox, const PVRTVec4 planes[4]) const;
	
	bool Load3dModelIndex(const char *pszFilename, CPVRTString* pErrorStr);
	void Release3dModelIndex();	
	void Cache3dModelData();

	bool LoadOcclusionData(const char *pszFilename, CPVRTString* pErrorStr);
	void ReleaseOcclusionData();
};


/*!****************************************************************************
 @Function		LoadTextures
 @Output		pErrorStr		A string describing the error on failure
 @Return		bool			true if no error occured
 @Description	Loads the textures required for this training course
******************************************************************************/
bool OGLES2Navigation3D::LoadTextures(CPVRTString* const pErrorStr)
{
	if (PVRTTextureLoadFromPVR(c_szTextureNameSkybox, &m_uiTextureIdSkybox) != PVR_SUCCESS)
	{
		*pErrorStr = CPVRTString("ERROR: Could not open texture file ") + c_szTextureNameSkybox;
		return false;
	}
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	m_uiNumTextures = sizeof(c_paszTextures)/sizeof(c_paszTextures[0]);
	m_pauiTextureIds = new GLuint[m_uiNumTextures];

	char buffer[512];
	for (unsigned int i=0; i < m_uiNumTextures; i++)
	{
		strcpy(buffer, c_paszTextures[i]);
		strcat(buffer, ".pvr");

		if (PVR_SUCCESS == PVRTTextureLoadFromPVR(buffer, &m_pauiTextureIds[i]))
		{
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		}
		else
		{
			PVRShellOutputDebug("Failed to load texture: %s\n", buffer);
		}
	}
	
	return true;
}


/*!****************************************************************************
 @Function		LoadShader
 @Output		pErrorStr		A string describing the error on failure
 @Input 		descr		    A struct containing all necessary information to
                                load and build the shader
 @Output		shader		    A struct containing all shader object identifiers
 @Return		bool			true if no error occured
 @Description	Loads and compiles a shader and links it into a shader program
******************************************************************************/
bool OGLES2Navigation3D::LoadShader(CPVRTString* pErrorStr, const ShaderDescription &descr, Shader &shader)
{
	if (PVRTShaderLoadFromFile(descr.pszVertShaderBinFile, descr.pszVertShaderSrcFile, GL_VERTEX_SHADER, GL_SGX_BINARY_IMG, &shader.uiVertexShaderId, pErrorStr) != PVR_SUCCESS)
	{
		*pErrorStr = descr.pszVertShaderSrcFile + CPVRTString(":\n") + *pErrorStr;
		return false;
	}

	if (PVRTShaderLoadFromFile(descr.pszFragShaderBinFile, descr.pszFragShaderSrcFile, GL_FRAGMENT_SHADER, GL_SGX_BINARY_IMG, &shader.uiFragmentShaderId, pErrorStr) != PVR_SUCCESS)
	{
		*pErrorStr = descr.pszFragShaderSrcFile + CPVRTString(":\n") + *pErrorStr;
		return false;
	}

	// Set up and link to the shader program
	if (PVRTCreateProgram(&shader.uiId, shader.uiVertexShaderId, shader.uiFragmentShaderId, descr.pszAttributes, descr.ui32NumAttributes, pErrorStr))
	{	
		*pErrorStr = descr.pszFragShaderSrcFile + CPVRTString(":\n") + *pErrorStr;
		return false;
	}

	return true;
}

/*!****************************************************************************
 @Function		LoadShaders
 @Output		pErrorStr		A string describing the error on failure
 @Return		bool			true if no error occured
 @Description	Loads and compiles shaders and links them to shader programs
******************************************************************************/
bool OGLES2Navigation3D::LoadShaders(CPVRTString* pErrorStr)
{
	//	Load and compile the shaders from files.
	//	Binary shaders are tried first, source shaders are used as fallback.		

	// CityModelShader
	//
	if (!LoadShader(pErrorStr, BuildingShaderDescription, m_BuildingShader))
	{
		PVRShellSet(prefExitMessage, pErrorStr->c_str());
		return false;
	}	
	
	m_BuildingShader.uiModelViewProjMatrixLoc = glGetUniformLocation(m_BuildingShader.uiId, "ModelViewProjMatrix");	
	m_BuildingShader.uiLightDirectionLoc      = glGetUniformLocation(m_BuildingShader.uiId, "LightDirection");
	glUniform1i(glGetUniformLocation(m_BuildingShader.uiId, "sTexture"), 0);

	//  SkyboxShader
	//
	if (!LoadShader(pErrorStr, SkyboxShaderDescription, m_SkyboxShader))
	{
		PVRShellSet(prefExitMessage, pErrorStr->c_str());
		return false;
	}

	m_SkyboxShader.uiModelViewProjMatrixLoc = glGetUniformLocation(m_SkyboxShader.uiId, "ModelViewProjMatrix");	
	glUniform1i(glGetUniformLocation(m_SkyboxShader.uiId, "sCubeMap"), 0);

	return true;
}


/*!****************************************************************************
 @Function		InitApplication
 @Return		bool		true if no error occured
 @Description	Code in InitApplication() will be called by PVRShell once per
				run, before the rendering context is created.
				Used to initialize variables that are not dependant on it
				(e.g. external modules, loading meshes, etc.)
				If the rendering context is lost, InitApplication() will
				not be called again.
******************************************************************************/
bool OGLES2Navigation3D::InitApplication()
{
	m_bPause = false;
	m_bUseOcclusionData = true;
	m_bViewFrustumCulling = true;
	
	m_paModelTiles = 0;
	m_pauiVisibleTiles = 0;
	m_paPositionOcclusionData = 0;
	m_pauiTextureIds = 0;

	m_uiNumTextures = 0;
	m_uiNumModelTiles = 0;
	m_uiNumVisibleTiles = 0;
	m_uiNumOcclusionData = 0;
	
	// Get and set the read path for content files
	CPVRTResourceFile::SetReadPath((char*)PVRShellGet(prefReadPath));

	CPVRTString errorStr;
	if (!Load3dModelIndex(c_szModelIndexFilename, &errorStr))
	{
		PVRShellOutputDebug(errorStr.c_str());
		return false;
	}

	if (!LoadOcclusionData(c_szOcclusionDataFilename, &errorStr))
	{
		PVRShellOutputDebug(errorStr.c_str());
		return false;
	}
	
	m_pauiVisibleTiles = new unsigned int[m_uiNumModelTiles];

	if (PVR_SUCCESS != m_CameraPod.ReadFromFile("cameratrack.pod"))
	{
		PVRShellSet(prefExitMessage, "Error: Failed to parse POD cameratrack.\n");
		return false;
	}	

	m_ActiveCameraTrack = 0;
	m_fNearClipPlane = m_CameraPod.pCamera->fNear;
	m_fFarClipPlane = m_CameraPod.pCamera->fFar;
	m_fFOV = m_CameraPod.pCamera->fFOV;

	const int cmdargs = PVRShellGet(prefCommandLineOptNum);
	const SCmdLineOpt *pCmdLine = (const SCmdLineOpt *)PVRShellGet(prefCommandLineOpts);
	for (int i=0; i < cmdargs; i++)
	{
		if (strcmp(pCmdLine[i].pArg, "-far") == 0)
		{
			PVRShellOutputDebug("Info: Changing far clip plane from %.0f to %s\n", m_fFarClipPlane, pCmdLine[i].pVal);
			m_fFarClipPlane = (float)atoi(pCmdLine[i].pVal);			
		}
		if (strcmp(pCmdLine[i].pArg, "-near") == 0)
		{
			PVRShellOutputDebug("Info: Changing near clip plane from %.0f to %s\n", m_fNearClipPlane, pCmdLine[i].pVal);
			m_fNearClipPlane = (float)atoi(pCmdLine[i].pVal);		
		}
	}

	m_afLodDistances[0] = (m_CameraPod.pCamera->fFar + m_CameraPod.pCamera->fNear) * 0.5f;
	m_afLodDistances[1] = m_CameraPod.pCamera->fFar;
	m_afSquaredLodDistances[0] = m_afLodDistances[0] * m_afLodDistances[0];
	m_afSquaredLodDistances[1] = m_afLodDistances[1] * m_afLodDistances[1];
				
	// Set timer variables
	m_fCameraAnimation = 0.0f;	
	m_fDebugTimeMultiplier = 0.015f;

	return true;
}


/*!****************************************************************************
 @Function		QuitApplication
 @Return		bool		true if no error occured
 @Description	Code in QuitApplication() will be called by PVRShell once per
				run, just before exiting the program.
				If the rendering context is lost, QuitApplication() will
				not be called.
******************************************************************************/
bool OGLES2Navigation3D::QuitApplication()
{	
	Release3dModelIndex();
	ReleaseOcclusionData();

	delete [] m_pauiTextureIds;
	delete [] m_pauiVisibleTiles;

    return true;
}

/*!****************************************************************************
 @Function		InitView
 @Return		bool		true if no error occured
 @Description	Code in InitView() will be called by PVRShell upon
				initialization or after a change in the rendering context.
				Used to initialize variables that are dependant on the rendering
				context (e.g. textures, vertex buffers, etc.)
******************************************************************************/
bool OGLES2Navigation3D::InitView()
{
	m_fAspectRatio = PVRShellGet(prefWidth) / (float)PVRShellGet(prefHeight);

	CPVRTString ErrorStr;

	// Load textures
	if (!LoadTextures(&ErrorStr))
	{
		PVRShellSet(prefExitMessage, ErrorStr.c_str());
		return false;
	}

	// Load and compile the shaders & link programs
	if (!LoadShaders(&ErrorStr))
	{
		PVRShellSet(prefExitMessage, ErrorStr.c_str());
		return false;
	}

	// Is the screen rotated?
	m_bRotate = PVRShellGet(prefIsRotated) && PVRShellGet(prefFullScreen);
#ifdef _WIN32
	if (PVRShellGet(prefWidth) < PVRShellGet(prefHeight))
		m_bRotate = true;
#endif

	// Initialize Print3D
	if(m_Print3D.SetTextures(0,PVRShellGet(prefWidth),PVRShellGet(prefHeight), m_bRotate) != PVR_SUCCESS)
	{
		PVRShellSet(prefExitMessage, "ERROR: Cannot initialise Print3D\n");
		return false;
	}

	Cache3dModelData();

	// Generate the skybox VBO
	GLfloat *pSkyboxVertices, *pSkyboxTexCoords;
	PVRTCreateSkybox(10.0f, true, 512, &pSkyboxVertices, &pSkyboxTexCoords);
	glGenBuffers(1, &m_uiSkyboxVBO);
	glBindBuffer(GL_ARRAY_BUFFER, m_uiSkyboxVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*3*24, pSkyboxVertices, GL_STATIC_DRAW);
	PVRTDestroySkybox(pSkyboxVertices, pSkyboxTexCoords);
	
	// No stencil test required
	glDisable(GL_STENCIL_TEST);

	// Disable blending by default
	glDisable(GL_BLEND);	
	
	// Paint it black
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);		

	// Texture channel 0 will always be active
	glActiveTexture(GL_TEXTURE0);

	// Setup the viewport for the whole window
	glViewport(0, 0, PVRShellGet(prefWidth), PVRShellGet(prefHeight));
				
	return true;
}

/*!****************************************************************************
 @Function		ReleaseShader
 @Return		bool		true if no error occured
 @Description	Releases a shader program including the individual shader objects.
******************************************************************************/
bool OGLES2Navigation3D::ReleaseShader(Shader &shader)
{
	glDeleteProgram(shader.uiId);	
	glDeleteShader(shader.uiVertexShaderId);
	glDeleteShader(shader.uiFragmentShaderId);
	return true;
}

/*!****************************************************************************
 @Function		ReleaseView
 @Return		bool		true if no error occured
 @Description	Code in ReleaseView() will be called by PVRShell when the
				application quits or before a change in the rendering context.
******************************************************************************/
bool OGLES2Navigation3D::ReleaseView()
{
	// Delete textures
	glDeleteTextures(1, &m_uiTextureIdSkybox);
	glDeleteTextures(m_uiNumTextures, m_pauiTextureIds);
		
	// Delete program and shader objects	
	ReleaseShader(m_BuildingShader);
	ReleaseShader(m_SkyboxShader);

	glDeleteBuffers(1, &m_uiSkyboxVBO);
		
	// Release Print3D Textures
	m_Print3D.ReleaseTextures();

	return true;
}

/*!****************************************************************************
 @Function		RenderScene
 @Return		bool		true if no error occured
 @Description	Main rendering loop function of the program. The shell will
				call this function every frame.
				eglSwapBuffers() will be performed by PVRShell automatically.
				PVRShell will also manage important OS events.
				The user has access to these events through an abstraction 
				layer provided by PVRShell.
******************************************************************************/
bool OGLES2Navigation3D::RenderScene()
{
	// Handle user input and update the timer based variables
	HandleInput();
	unsigned int fps = UpdateTimer();				

	// Update the camera interpolation and extract required matrices
	CalculateCameraMatrices();	

	// Clear the color and depth buffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	

	// Disable depth test to render the skybox
	glDisable(GL_DEPTH_TEST);
	RenderSkyBox();

	// Enable depth test and render buildings
	glEnable(GL_DEPTH_TEST);

	if (m_bUseOcclusionData)
		Render3dModelsOcclusion();
	else
		Render3dModels();
	
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	
	// Displays the demo name and other information using the Print3D tool. 
	// For a detailed explanation, see the training course IntroducingPVRTools	
	m_Print3D.DisplayDefaultTitle("Navigation", NULL, ePVRTPrint3DLogoIMG);	
#ifdef ENABLE_ADVANCED_OUTPUT
	m_Print3D.Print3D(0.05f, 81.0f, 1.0f, 0xFFFFFFFF, "%dx%d FPS: %d", PVRShellGet(prefWidth), PVRShellGet(prefHeight), fps);
#endif
#ifdef ENABLE_CULLING_OPTIONS
	m_Print3D.Print3D(0.05f, 88.0f, 1.0f, 0xFFFFFFFF, "Occlusion culling: %s", (m_bUseOcclusionData ? "On" : "Off"));
	m_Print3D.Print3D(0.05f, 95.0f, 1.0f, 0xFFFFFFFF, "Frustum culling: %s", (m_bViewFrustumCulling ? "On" : "Off"));
#endif
	m_Print3D.Flush();

	return true;
}

/*!****************************************************************************
 @Function		UpdateTimer
 @Input			none
 @Description	Updates the values of the current time, previous time, current time
				in seconds, delta time and the FPS counter used in the program
******************************************************************************/
unsigned int OGLES2Navigation3D::UpdateTimer()
{	
	static unsigned int framecounter = 0;	
	static unsigned long ulPreviousTime = PVRShellGetTime();
	static unsigned long ulLastUpdate = PVRShellGetTime();
	static unsigned long ulLastFpsUpdate = PVRShellGetTime();
	static unsigned int fps = 60;

	unsigned long ulCurrentTime = PVRShellGetTime();
	unsigned long ulTimeDelta = ulCurrentTime - ulPreviousTime;
	ulPreviousTime = ulCurrentTime;

	// Update the visible object four times per second
	if (ulCurrentTime - ulLastUpdate > 100)
	{
		ulLastUpdate = ulCurrentTime;
		Update3dModelWorkingset();
	}

	if (ulCurrentTime - ulLastFpsUpdate > 1000)
	{
		ulLastFpsUpdate = ulCurrentTime;		
		fps = framecounter;
		framecounter = 0;
	}
	else
		framecounter++;

	// Advance camera animation when not paused
	if (!m_bPause)
	{
		m_fCameraAnimation += ulTimeDelta * m_fDebugTimeMultiplier;	
		// Start from beginning when the end is near
		if (m_fCameraAnimation > (m_CameraPod.nNumFrame - 1))
		{
			m_fCameraAnimation = 0.0f;
			// Jump to next camera track 
			m_ActiveCameraTrack = ++m_ActiveCameraTrack % m_CameraPod.nNumCamera;
		}
	}

	return fps;
}

/*!****************************************************************************
 @Function		Update3dModelWorkingset
 @Input			none
 @Description	Updates the visible object set based on the camera position and
                the intersections of the view frustum with the global map plane.
******************************************************************************/
void OGLES2Navigation3D::Update3dModelWorkingset()
{		
	PVRTVec4 planes[4];
	ExtractViewFrustumPlanes(planes[0], planes[1], planes[2], planes[3]);

	const PVRTVec2 lodCenter(m_vCameraFrom);

	m_uiNumVisibleTiles = 0;

	// Update the object set for each layer
	for (unsigned int i=0; i < m_uiNumModelTiles; i++)
	{	
		const PVRTBoundingBox2D bbox = m_paModelTiles[i].boundingbox;
		
		if (BoundingBoxIntersectsFrustum(bbox, planes))
		{
			const float distToCameraSquared = ((bbox.maxCoords + bbox.minCoords) * 0.5f - lodCenter).lenSqr();
			unsigned int lod = (unsigned int)(m_paModelTiles[i].numLod - 1);

			for (unsigned int j=0; j < m_paModelTiles[i].numLod; j++)
			{
				if (distToCameraSquared < m_afSquaredLodDistances[j])
				{	
					lod = j;
					break;
				}
			}

			m_pauiVisibleTiles[m_uiNumVisibleTiles++] = i * 10 + lod;
		}
	}

	// Fine grained culling
	for (unsigned int i=0; i < m_uiNumVisibleTiles; i++)
	{		
		const unsigned int model = m_pauiVisibleTiles[i] / 10;
		const unsigned int lod = m_pauiVisibleTiles[i] - model * 10;		

		ModelTileLod *pLod = &m_paModelTiles[model].paLod[lod];
		pLod->numVisibleNodes = 0;

		if (m_bViewFrustumCulling)
		{
			// Check each node in each tile against the view frustum and add it to the list
			// of visible nodes if it is visible
			for (unsigned int j=0; j < pLod->numObjects; j++)
				if (BoundingBoxIntersectsFrustum(pLod->paObjects[j].boundingbox, planes))
					pLod->paVisibleNodes[pLod->numVisibleNodes++] = j;
		}
		else
			for (unsigned int j=0; j < pLod->numObjects; j++)
				pLod->paVisibleNodes[pLod->numVisibleNodes++] = j;
	}
}


/*!****************************************************************************
 @Function		HandleInput
 @Description	Handles user input.
******************************************************************************/
void OGLES2Navigation3D::HandleInput()
{
	// Handle user input
	if (PVRShellIsKeyPressed(PVRShellKeyNameDOWN))
		m_bPause = !m_bPause;

	if (PVRShellIsKeyPressed(PVRShellKeyNameUP))
		m_ActiveCameraTrack = ++m_ActiveCameraTrack % m_CameraPod.nNumCamera;

#ifdef ENABLE_CULLING_OPTIONS
	if (PVRShellIsKeyPressed(PVRShellKeyNameLEFT))
		m_bViewFrustumCulling = !m_bViewFrustumCulling;

	if (PVRShellIsKeyPressed(PVRShellKeyNameRIGHT))
		m_bUseOcclusionData = !m_bUseOcclusionData;
#endif 

	if (PVRShellIsKeyPressed(PVRShellKeyNameACTION1))
	{
		PVRShellOutputDebug("Reloading shader ... \n");
		CPVRTString ErrorStr;
		ReleaseShader(m_BuildingShader);
		if (!LoadShader(&ErrorStr, BuildingShaderDescription, m_BuildingShader))
			PVRShellOutputDebug("Failed to reload shader: %s\n", ErrorStr.c_str());
		else
			PVRShellOutputDebug("Shader reload succesfull.\n");
	}

#if 0
	if (PVRShellIsKeyPressed(PVRShellKeyNameRIGHT))
		m_fDebugTimeMultiplier *= 2.0f;
	if (PVRShellIsKeyPressed(PVRShellKeyNameLEFT))
		m_fDebugTimeMultiplier *= 0.5f;
#endif
}


/*!****************************************************************************
 @Function		Render3dModels 
 @Description	Draws a SPODMesh after the model view matrix has been set and
				the meterial prepared.
******************************************************************************/
void OGLES2Navigation3D::Render3dModels()
{	
	PVRTVec4 planes[4];
	ExtractViewFrustumPlanes(planes[0], planes[1], planes[2], planes[3]);
	
	PVRTVec3 lightdir = PVRTVec3(0.5f, 0.5f, 1.0f).normalize();

	glUseProgram(m_BuildingShader.uiId);
	glUniformMatrix4fv(m_BuildingShader.uiModelViewProjMatrixLoc, 1, GL_FALSE, m_mViewProjectionMatrix.ptr());
	glUniform3fv(m_BuildingShader.uiLightDirectionLoc, 1, lightdir.ptr());

	// Enable the vertex attribute arrays
	glEnableVertexAttribArray(VERTEX_ARRAY);
	glEnableVertexAttribArray(NORMAL_ARRAY);
	glEnableVertexAttribArray(TEXCOORD_ARRAY);

	GLuint prevTexture = 0;

	for (unsigned int i=0; i < m_uiNumVisibleTiles; i++)
	{	
		const unsigned int entry = m_pauiVisibleTiles[i];
		const unsigned int model = entry / 10;
		const unsigned int lod = entry - model * 10;		

		const ModelTileLod *pLod = &m_paModelTiles[model].paLod[lod];
		if (!pLod->bLoaded)
			continue;

		glBindBuffer(GL_ARRAY_BUFFER, pLod->vbos[0]);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pLod->vbos[1]);
		
		glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, sizeof(PVRTModelVertex), 0);
		glVertexAttribPointer(NORMAL_ARRAY, 3, GL_FLOAT, GL_FALSE, sizeof(PVRTModelVertex), (const void*)offsetof(PVRTModelVertex, normal));	
		glVertexAttribPointer(TEXCOORD_ARRAY, 2, GL_FLOAT, GL_FALSE, sizeof(PVRTModelVertex), (const void*)offsetof(PVRTModelVertex, texcoord));		

		for (unsigned int j=0; j < pLod->numVisibleNodes; j++)
		{
			const ModelTileObjectSet *pObjectSet = &pLod->paObjects[pLod->paVisibleNodes[j]];
			
			// Ignore the object if it is not visible
			if (m_bViewFrustumCulling && !BoundingBoxIntersectsFrustum(pObjectSet->boundingbox, planes))
					continue;

			for (unsigned int k=0; k < pObjectSet->numSubObjects; k++)
			{				
				if (pObjectSet->pauiTextures[k] != prevTexture)
				{
					glBindTexture(GL_TEXTURE_2D, pObjectSet->pauiTextures[k]);
					prevTexture = pObjectSet->pauiTextures[k];
				}
				glDrawElements(GL_TRIANGLES, (GLsizei)pObjectSet->paNumIndices[k], GL_UNSIGNED_SHORT, (void *)(pObjectSet->paIndexOffsets[k]*sizeof(GLushort)));				
			}
		}
	}

	glDisableVertexAttribArray(VERTEX_ARRAY);
	glDisableVertexAttribArray(NORMAL_ARRAY);
	glDisableVertexAttribArray(TEXCOORD_ARRAY);
}


/*!****************************************************************************
 @Function		Render3dModelsOcclusion 
 @Description	Draws a SPODMesh after the model view matrix has been set and
				the meterial prepared.
******************************************************************************/
void OGLES2Navigation3D::Render3dModelsOcclusion()
{	
	// Find the nearest spot containing occlusion data
	unsigned int nearest_pos = 0;
	float nearest_pos_dist = 99999999999.9f;
	for (unsigned int i=0; i < m_uiNumOcclusionData; i++)
	{					
		float dist = (m_paPositionOcclusionData[i].position - m_vCameraFrom).lenSqr();
		if (dist < nearest_pos_dist)
		{
			nearest_pos_dist = dist;
			nearest_pos = i;
		}
	}

	PVRTVec4 planes[4];
	ExtractViewFrustumPlanes(planes[0], planes[1], planes[2], planes[3]);
			
	PVRTVec3 lightdir = PVRTVec3(0.5f, 0.5f, 1.0f).normalize();

	glUseProgram(m_BuildingShader.uiId);
	glUniformMatrix4fv(m_BuildingShader.uiModelViewProjMatrixLoc, 1, GL_FALSE, m_mViewProjectionMatrix.ptr());
	glUniform3fv(m_BuildingShader.uiLightDirectionLoc, 1, lightdir.ptr());
	
	// Enable the vertex attribute arrays
	glEnableVertexAttribArray(VERTEX_ARRAY);
	glEnableVertexAttribArray(NORMAL_ARRAY);
	glEnableVertexAttribArray(TEXCOORD_ARRAY);

	GLuint prevTexture = 0;

	PositionOcclusionData *pRefData = &m_paPositionOcclusionData[nearest_pos];
	for (unsigned int i=0; i < pRefData->numRefObjects; i++)
	{
		const unsigned int numRefObjects = pRefData->pNumRefObject[i];
		const unsigned int refTile = pRefData->pRefTile[i];
		const unsigned int *pRefObjects = pRefData->ppRefObjects[i];		
		
		const PVRTVec2 lodReferencePosition(m_vCameraFrom);
		const float sqDist = ((m_paModelTiles[refTile].boundingbox.maxCoords + m_paModelTiles[refTile].boundingbox.minCoords) * 0.5f - lodReferencePosition).lenSqr();
		unsigned int lod = (unsigned int)(m_paModelTiles[i].numLod - 1);
		for (unsigned int j=0; j < m_paModelTiles[refTile].numLod; j++)
		{
			if (sqDist < m_afSquaredLodDistances[j])
			{	
				lod = j;
				break;
			}
		}
		 
		const ModelTileLod *pLod = &m_paModelTiles[refTile].paLod[lod];
		if (!pLod->bLoaded)
			continue;
		
		// Ignore the tile if it is not visible
		if (m_bViewFrustumCulling && !BoundingBoxIntersectsFrustum(m_paModelTiles[refTile].boundingbox, planes))
			continue;

		glBindBuffer(GL_ARRAY_BUFFER, pLod->vbos[0]);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pLod->vbos[1]);

		for (unsigned int j=0; j < numRefObjects; j++)
		{			
			const ModelTileObjectSet *pObjectSet = &pLod->paObjects[pRefObjects[j]];
			
			// Ignore the object if it is not visible
			if (m_bViewFrustumCulling && !BoundingBoxIntersectsFrustum(pObjectSet->boundingbox, planes))
					continue;

			for (unsigned int k=0; k < pObjectSet->numSubObjects; k++)
			{				
				glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, sizeof(PVRTModelVertex), 0);
				glVertexAttribPointer(NORMAL_ARRAY, 3, GL_FLOAT, GL_FALSE, sizeof(PVRTModelVertex), (const void*)offsetof(PVRTModelVertex, normal));	
				glVertexAttribPointer(TEXCOORD_ARRAY, 2, GL_FLOAT, GL_FALSE, sizeof(PVRTModelVertex), (const void*)offsetof(PVRTModelVertex, texcoord));		

				if (pObjectSet->pauiTextures[k] != prevTexture)
				{
					glBindTexture(GL_TEXTURE_2D, pObjectSet->pauiTextures[k]);
					prevTexture = pObjectSet->pauiTextures[k];
				}
				glDrawElements(GL_TRIANGLES, (GLsizei)pObjectSet->paNumIndices[k], GL_UNSIGNED_SHORT, (void *)(pObjectSet->paIndexOffsets[k]*sizeof(GLushort)));				
			}
		}
	}

	glDisableVertexAttribArray(VERTEX_ARRAY);
	glDisableVertexAttribArray(NORMAL_ARRAY);
	glDisableVertexAttribArray(TEXCOORD_ARRAY);

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}



/*!****************************************************************************
 @Function		RenderSkyBox
 @Description	TBD
******************************************************************************/
void OGLES2Navigation3D::RenderSkyBox()
{	
	PVRTMat4 mv_matrix = PVRTMat4::LookAtRH(PVRTVec3(0.0f, 0.0f, -2.5f), m_vCameraTo - m_vCameraFrom + PVRTVec3(0.0f, 0.0f, -2.5f), m_vCameraUp); 
	PVRTMat4 mvp_matrix = m_mProjectionMatrix * mv_matrix;
				
	glUseProgram(m_SkyboxShader.uiId);	
	glUniformMatrix4fv(m_SkyboxShader.uiModelViewProjMatrixLoc, 1, GL_FALSE, mvp_matrix.ptr());

	glBindBuffer(GL_ARRAY_BUFFER, m_uiSkyboxVBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindTexture(GL_TEXTURE_2D, m_uiTextureIdSkybox);

	// Enable the vertex attribute arrays
	glEnableVertexAttribArray(VERTEX_ARRAY);
	glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, sizeof(PVRTVec3), NULL);		
	
	// Draw primitive
	for(int i = 0; i < 6; ++i)
		glDrawArrays(GL_TRIANGLE_STRIP, i*4, 4);

	glDisableVertexAttribArray(VERTEX_ARRAY);		
}


/*!****************************************************************************
 @Function		CreateModelVbo
 @Description	Creates vertex buffer objects of 
******************************************************************************/
void OGLES2Navigation3D::CreateModelVbo(const unsigned int tile, const unsigned int lod)
{
	ModelTileLod *pLod = &m_paModelTiles[tile].paLod[lod];	

	CPVRTModelPOD model;
	if (PVR_FAIL == model.ReadFromFile(pLod->pszFilename))
			return;
	else
		pLod->bLoaded = true;

	unsigned int totalVertexCount = 0;
	unsigned int totalIndexCount = 0;

	for (unsigned int i=0; i < pLod->numObjects; i++)
	{		
		const size_t numSubObjects = pLod->paObjects[i].numSubObjects;

		pLod->paObjects[i].paNumIndices = new unsigned int[numSubObjects];
		pLod->paObjects[i].paIndexOffsets = new unsigned int[numSubObjects];
		pLod->paObjects[i].pauiTextures = new GLuint[numSubObjects];

		for (unsigned int j=0; j < pLod->paObjects[i].numSubObjects; j++)
		{			
			const int mesh_index = model.pNode[pLod->paObjects[i].pNodeIdx[j]].nIdx;
			const SPODMesh &mesh = model.pMesh[mesh_index];			
			
			totalVertexCount += mesh.nNumVertex;
			totalIndexCount += PVRTModelPODCountIndices(mesh);

			pLod->paObjects[i].paNumIndices[j] = PVRTModelPODCountIndices(mesh);			

			if (model.pMaterial)
			{
				const int material_index = model.pNode[pLod->paObjects[i].pNodeIdx[j]].nIdxMaterial;
				const SPODTexture &texture = model.pTexture[model.pMaterial[material_index].nIdxTexDiffuse];
				// Pre-init with invalid texture handle
				pLod->paObjects[i].pauiTextures[j] = 0;

				for (unsigned int k=0; k < m_uiNumTextures; k++)
				{
					if (strcmp(texture.pszName, c_paszTextures[k]) == 0)
					{
						pLod->paObjects[i].pauiTextures[j] = m_pauiTextureIds[k];
						break;
					}						
				}
			}
		}
	}

	if (totalVertexCount > 65536)
	{
		PVRShellOutputDebug("Too many vertices to index with ushort in mesh %s!\n", pLod->pszFilename);
		pLod->bLoaded = false;
		return;
	}

	GLushort *pIndices = new GLushort[totalIndexCount];
	PVRTModelVertex *pVertices = new PVRTModelVertex[totalVertexCount];

	unsigned int indexOffset = 0;
	unsigned int vertexOffset = 0;
	for (unsigned int i=0; i < pLod->numObjects; i++)
	{			
		for (unsigned int j=0; j < pLod->paObjects[i].numSubObjects; j++)
		{			
			const int mesh_index = model.pNode[pLod->paObjects[i].pNodeIdx[j]].nIdx;
			const SPODMesh &mesh = model.pMesh[mesh_index];			
			
			const unsigned int numIndices = PVRTModelPODCountIndices(mesh);						

			PVRTModelVertex *pSrcVertices = (PVRTModelVertex *)mesh.pInterleaved;
			GLushort *pSrcIndices = (GLushort *)mesh.sFaces.pData;

			pLod->paObjects[i].paIndexOffsets[j] = indexOffset;

			for (unsigned int k=0; k < mesh.nNumVertex; k++)
			{	
				const PVRTVec3 normal = pSrcVertices[k].normal;
				pVertices[vertexOffset + k].normal = PVRTVec3(normal.x, -normal.z, normal.y);
				const PVRTVec3 pos = pSrcVertices[k].position;
				pVertices[vertexOffset + k].position = PVRTVec3(pos.x, -pos.z, pos.y);
				pVertices[vertexOffset + k].texcoord = pSrcVertices[k].texcoord;
			}
			
			for (unsigned int k=0; k < numIndices; k++)
				pIndices[indexOffset + k] = (GLushort)(pSrcIndices[k] + vertexOffset);

			vertexOffset += mesh.nNumVertex;
			indexOffset += numIndices;
		}
	}

	glGenBuffers(2, pLod->vbos);
	glBindBuffer(GL_ARRAY_BUFFER, pLod->vbos[0]);
	glBufferData(GL_ARRAY_BUFFER, totalVertexCount * sizeof(PVRTModelVertex), pVertices, GL_STATIC_DRAW); 
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pLod->vbos[1]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndexCount * sizeof(GLushort), pIndices, GL_STATIC_DRAW); 

	delete [] pIndices;
	delete [] pVertices;
}

/*!****************************************************************************
 @Function		ReleaseModelVbo
 @Description	TODO
******************************************************************************/
void OGLES2Navigation3D::ReleaseModelVbo(const unsigned int tile, const unsigned int lod)
{
	ModelTileLod *pLod = &m_paModelTiles[tile].paLod[lod];

	for (unsigned int i=0; i < pLod->numObjects; i++)
	{		
		delete [] pLod->paObjects[i].paNumIndices;
		delete [] pLod->paObjects[i].paIndexOffsets;
		delete [] pLod->paObjects[i].pauiTextures;

		pLod->paObjects[i].paNumIndices = 0;
		pLod->paObjects[i].paIndexOffsets = 0;
		pLod->paObjects[i].pauiTextures = 0;
	}
}

/*!****************************************************************************
 @Function		Cache3dModelData
 @Description	Loads all POD files and textures in advance.
******************************************************************************/
void OGLES2Navigation3D::Cache3dModelData()
{	
	for (unsigned int i=0; i < m_uiNumModelTiles; i++)
		for (unsigned int j=0; j < m_paModelTiles[i].numLod; j++)
			CreateModelVbo(i, j);
}


/*!****************************************************************************
 @Function		GetCameraFrame
 @Description	TODO
******************************************************************************/
void OGLES2Navigation3D::GetCameraFrame(PVRTVec3 &from, PVRTVec3 &to, PVRTVec3 &up, float time)
{	
	m_CameraPod.SetFrame(time);

	m_CameraPod.GetCamera(from, to, up, m_ActiveCameraTrack);
	from *= 0.0254f;
	to *= 0.0254f;

	from = PVRTVec3(from.x, -from.z, from.y);
	to = PVRTVec3(to.x, -to.z, to.y);
	up = PVRTVec3(up.x, -up.z, up.y);
}

/*!****************************************************************************
 @Function		CalculateCameraMatrices
 @Description	TODO
******************************************************************************/
void OGLES2Navigation3D::CalculateCameraMatrices()
{	
	float clamped_delta = m_fCameraAnimation + 5.0f;
	if (clamped_delta > (m_CameraPod.nNumFrame - 1))
		clamped_delta -= (m_CameraPod.nNumFrame - 1);

	PVRTVec3 now_from, now_to, now_up;
	GetCameraFrame(now_from, now_to, now_up, m_fCameraAnimation);

	PVRTVec3 next_from, next_to, next_up;
	GetCameraFrame(next_from, next_to, next_up, clamped_delta);

	m_vCameraFrom = now_from;
	m_vCameraTo = now_from + (next_from - now_from).normalized();	
	m_vCameraUp = now_up;

	m_mViewMatrix = PVRTMat4::LookAtRH(m_vCameraFrom, m_vCameraTo, m_vCameraUp); 	
	m_mProjectionMatrix = PVRTMat4::PerspectiveFovRH(m_fFOV, m_fAspectRatio, m_fNearClipPlane, m_fFarClipPlane, PVRTMat4::OGL, m_bRotate); 
	m_mViewProjectionMatrix = m_mProjectionMatrix * m_mViewMatrix;
}

/*!****************************************************************************
 @Function		ExtractViewFrustumPlanes
 @Description	Extracts the (left, right, front and back) view frustum planes
                from the camera modelview-projection matrix.
******************************************************************************/
void OGLES2Navigation3D::ExtractViewFrustumPlanes(PVRTVec4 &left, PVRTVec4 &right, PVRTVec4 &front, PVRTVec4 &back) const
{
	const PVRTMat4 matrix = m_mViewProjectionMatrix;

	left.x = (matrix.f[3]  + matrix.f[0]);
    left.y = (matrix.f[7]  + matrix.f[4]);
    left.z = (matrix.f[11] + matrix.f[8]);
	left.w = (matrix.f[15] + matrix.f[12]);
	float inv_len = 1.0f / PVRTVec3(left).length();
	left *= inv_len;

	right.x = (matrix.f[3]  - matrix.f[0]);
    right.y = (matrix.f[7]  - matrix.f[4]);
    right.z = (matrix.f[11] - matrix.f[8]);
	right.w = (matrix.f[15] - matrix.f[12]);
	inv_len = 1.0f / PVRTVec3(right).length();
	right *= inv_len;

	front.x = (matrix.f[3]  + matrix.f[2]);
    front.y = (matrix.f[7]  + matrix.f[6]);
    front.z = (matrix.f[11] + matrix.f[10]);
	front.w = (matrix.f[15] + matrix.f[14]);
	inv_len = 1.0f / PVRTVec3(front).length();
	front *= inv_len;

	back.x = (matrix.f[3]  - matrix.f[2]);
    back.y = (matrix.f[7]  - matrix.f[6]);
    back.z = (matrix.f[11] - matrix.f[10]);
	back.w = (matrix.f[15] - matrix.f[14]);
	inv_len = 1.0f / PVRTVec3(back).length();
	back *= inv_len;
}



/*!****************************************************************************
 @Function		Load3dModelIndex
 @Description	Loads the mesh data required for this training course into
				vertex buffer objects
******************************************************************************/
bool OGLES2Navigation3D::Load3dModelIndex(const char *pszFilename, CPVRTString* pErrorStr)
{
	CPVRTResourceFile file(pszFilename);
	if (!file.IsOpen())
	{
		*pErrorStr = "Error: Could not open 3d model hirarchy file!\n";
		return false;
	}

	const char *pData = (const char *)file.DataPtr();
	
	size_t numTiles;
	memcpy((char *)&numTiles, pData, sizeof(numTiles));
	pData += sizeof(numTiles);

	m_uiNumModelTiles = (unsigned int)numTiles;
	m_paModelTiles = new ModelTile[m_uiNumModelTiles];

	for (unsigned int i=0; i < m_uiNumModelTiles; i++)
	{
		memcpy((char *)&m_paModelTiles[i].boundingbox, pData, sizeof(PVRTBoundingBox2D));
		pData += sizeof(PVRTBoundingBox2D);		
		memcpy((char *)&m_paModelTiles[i].numLod, pData, sizeof(size_t));
		pData += sizeof(size_t);		
	
		m_paModelTiles[i].paLod = new ModelTileLod[m_paModelTiles[i].numLod];

		for (unsigned int j=0; j < m_paModelTiles[i].numLod; j++)
		{			
			m_paModelTiles[i].paLod[j].bLoaded = false;

			size_t namelength;
			memcpy((char *)&namelength, pData, sizeof(size_t));
			pData += sizeof(size_t);
						
			m_paModelTiles[i].paLod[j].pszFilename = new char[namelength+1];
			memcpy((char *)m_paModelTiles[i].paLod[j].pszFilename, pData, sizeof(char) * namelength);
			pData += sizeof(char) * namelength;
			m_paModelTiles[i].paLod[j].pszFilename[namelength] = '\0';
			
			memcpy((char *)&m_paModelTiles[i].paLod[j].numObjects, pData, sizeof(size_t));
			pData += sizeof(size_t);
			m_paModelTiles[i].paLod[j].paObjects = new ModelTileObjectSet[m_paModelTiles[i].paLod[j].numObjects];

			m_paModelTiles[i].paLod[j].paVisibleNodes = new unsigned int[m_paModelTiles[i].paLod[j].numObjects];
			m_paModelTiles[i].paLod[j].numVisibleNodes = 0;
						
			for (unsigned int k=0; k < m_paModelTiles[i].paLod[j].numObjects; k++)
			{			
				memcpy((char *)&m_paModelTiles[i].paLod[j].paObjects[k].boundingbox, pData, sizeof(PVRTBoundingBox2D));
				pData += sizeof(PVRTBoundingBox2D);
				memcpy((char *)&m_paModelTiles[i].paLod[j].paObjects[k].numSubObjects, pData, sizeof(size_t));
				pData += sizeof(size_t);				
				
				m_paModelTiles[i].paLod[j].paObjects[k].pNodeIdx = new unsigned int[m_paModelTiles[i].paLod[j].paObjects[k].numSubObjects];

				memcpy((char *)m_paModelTiles[i].paLod[j].paObjects[k].pNodeIdx, pData, sizeof(unsigned int) * m_paModelTiles[i].paLod[j].paObjects[k].numSubObjects);
				pData += sizeof(unsigned int) * m_paModelTiles[i].paLod[j].paObjects[k].numSubObjects;				
				
				m_paModelTiles[i].paLod[j].paObjects[k].pauiTextures = 0;
				m_paModelTiles[i].paLod[j].paObjects[k].paNumIndices = 0;
				m_paModelTiles[i].paLod[j].paObjects[k].paIndexOffsets = 0;
			}
		}
	}

	return true;
}

/*!****************************************************************************
 @Function		Release3dModelIndex
 @Description	Loads the mesh data required for this training course into
				vertex buffer objects
******************************************************************************/
void OGLES2Navigation3D::Release3dModelIndex()
{
	for (unsigned int i=0; i < m_uiNumModelTiles; i++)
	{
		for (unsigned int j=0; j < m_paModelTiles[i].numLod; j++)
		{
			for (unsigned int k=0; k < m_paModelTiles[i].paLod[j].numObjects; k++)
			{
				delete [] m_paModelTiles[i].paLod[j].paObjects[k].pNodeIdx;
				delete [] m_paModelTiles[i].paLod[j].paObjects[k].paNumIndices;
				delete [] m_paModelTiles[i].paLod[j].paObjects[k].paIndexOffsets;
				delete [] m_paModelTiles[i].paLod[j].paObjects[k].pauiTextures;				
			}
			delete [] m_paModelTiles[i].paLod[j].paObjects;
			delete [] m_paModelTiles[i].paLod[j].paVisibleNodes;
			delete [] m_paModelTiles[i].paLod[j].pszFilename;						
		}
		delete [] m_paModelTiles[i].paLod;		
	}
	delete [] m_paModelTiles;
}


/*!****************************************************************************
 @Function		LoadOcclusionData
 @Description	TODO
******************************************************************************/
bool OGLES2Navigation3D::LoadOcclusionData(const char *pszFilename, CPVRTString* pErrorStr)
{
	CPVRTResourceFile file(pszFilename);
	if (!file.IsOpen())
	{
		*pErrorStr = "Error: Could not open occlusion data!\n";
		return false;
	}

	const char *pData = (const char *)file.DataPtr();

	size_t namelen;
	memcpy((char *)&namelen, pData, sizeof(namelen));
	pData += sizeof(namelen);
	// Skip the name
	pData += namelen;
		
	size_t numTiles;
	memcpy((char *)&numTiles, pData, sizeof(numTiles));
	pData += sizeof(numTiles);
	for (unsigned int i=0; i < numTiles; i++)
	{
		memcpy((char *)&namelen, pData, sizeof(namelen));
		pData += sizeof(namelen);
		// Skip the name
		pData += namelen;		
	}

	size_t numpositions;
	memcpy((char *)&numpositions, pData, sizeof(numpositions));
	pData += sizeof(numpositions);
	m_uiNumOcclusionData = (unsigned int)numpositions;

	m_paPositionOcclusionData = new PositionOcclusionData[m_uiNumOcclusionData];
	
	for (unsigned int i=0; i < m_uiNumOcclusionData; i++)
	{		
		memcpy((char *)&m_paPositionOcclusionData[i].position, pData, sizeof(PVRTVec3));
		pData += sizeof(PVRTVec3);
		
		size_t reftiles;		
		memcpy((char *)&reftiles, pData, sizeof(reftiles));
		pData += sizeof(reftiles);

		m_paPositionOcclusionData[i].numRefObjects = (unsigned int)reftiles;
		m_paPositionOcclusionData[i].pRefTile = new unsigned int[reftiles];
		m_paPositionOcclusionData[i].pNumRefObject = new unsigned int[reftiles];
		m_paPositionOcclusionData[i].ppRefObjects = new unsigned int*[reftiles];

		for (unsigned int j=0; j < reftiles; j++)
		{					
			size_t tilenum;
			memcpy((char *)&tilenum, pData, sizeof(tilenum));
			pData += sizeof(tilenum);
			m_paPositionOcclusionData[i].pRefTile[j] = (unsigned int)tilenum;
											
			size_t num_ref_models;			
			memcpy((char *)&num_ref_models, pData, sizeof(num_ref_models));
			pData += sizeof(num_ref_models);

			m_paPositionOcclusionData[i].pNumRefObject[j] = (unsigned int)num_ref_models;
			m_paPositionOcclusionData[i].ppRefObjects[j] = new unsigned int[num_ref_models];			
			memcpy((char *)m_paPositionOcclusionData[i].ppRefObjects[j], pData, sizeof(unsigned int) * num_ref_models);
			pData += sizeof(unsigned int) * num_ref_models;			
		}								
	}

	return true;
}

/*!****************************************************************************
 @Function		ReleaseOcclusionData
 @Description	TODO
******************************************************************************/
void OGLES2Navigation3D::ReleaseOcclusionData()
{
	for (unsigned int i=0; i < m_uiNumOcclusionData; i++)
	{
		for (unsigned int j=0; j < m_paPositionOcclusionData[i].numRefObjects; j++)
			delete [] m_paPositionOcclusionData[i].ppRefObjects[j];
		delete [] m_paPositionOcclusionData[i].pNumRefObject;
		delete [] m_paPositionOcclusionData[i].pRefTile;
		delete [] m_paPositionOcclusionData[i].ppRefObjects;
	}

	delete [] m_paPositionOcclusionData;
}

/*!****************************************************************************
 @Function		BoundingBoxIntersectsFrustum
 @Description	TODO
******************************************************************************/
bool OGLES2Navigation3D::BoundingBoxIntersectsFrustum(const PVRTBoundingBox2D &bbox, const PVRTVec4 planes[4]) const
{
	PVRTVec3 points[4];
	points[0] = PVRTVec3(bbox.minCoords.x, bbox.minCoords.y, 0.0f);
	points[1] = PVRTVec3(bbox.maxCoords.x, bbox.minCoords.y, 0.0f);
	points[2] = PVRTVec3(bbox.maxCoords.x, bbox.maxCoords.y, 0.0f);
	points[3] = PVRTVec3(bbox.minCoords.x, bbox.maxCoords.y, 0.0f);

	// Test the axis-aligned bounding box against each plane;
	// only cull if all points are outside of one the view frustum planes
	for (unsigned int p=0; p < 4; p++)
	{
		unsigned int pointsOut = 0;

		// Test the points against the plane
		for(unsigned int j=0; j < 4; j++)
			if ((planes[p].x * points[j].x + planes[p].y * points[j].y + planes[p].z * points[j].z + planes[p].w) < 0.0f)
				pointsOut++;

		// if all points are outside of a plane we can cull it
		if (pointsOut == 4)
			return false;
	}

	return true;
}


/*!****************************************************************************
 @Function		NewDemo
 @Return		PVRShell*		The demo supplied by the user
 @Description	This function must be implemented by the user of the shell.
				The user should return its PVRShell object defining the
				behaviour of the application.
******************************************************************************/
PVRShell* NewDemo()
{
	return new OGLES2Navigation3D();
}

/******************************************************************************
 End of file (OGLES2Navigation3D.cpp)
******************************************************************************/

